/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.heron-language.com
*/

#ifndef HERON_INTERPRETER_HPP
#define HERON_INTERPRETER_HPP

#include <algorithm>

struct HeronInterpreter
{
	LuaMachine luavm;
	lls::Module* module;
	heron_ast::HeronAst ast; 
	std::string sErrMsg;
	std::string sOutFile;
	bool bPreprocess;

	HeronInterpreter()
		: module(NULL), bPreprocess(false)
	{ 
		sOutFile = "";
	}
	
	~HeronInterpreter()
	{
		Clear();
	}

	void Clear() {
		sErrMsg = "";
		sOutFile = "";
		ast.Clear();
		delete module;
		module = NULL;
		luavm.Clear();
	}

	bool RunLuaFile(ConstString sFile) 
	{
		try 
		{
			String sCode = ReadFile(sFile);
			luavm.Clear();
			luavm.Eval(sCode.c_str());
			system("pause");
		}
		catch (CVMLException e)
		{
			sErrMsg = e.msg;
			printf("%s\n", sErrMsg.c_str());
			system("pause");
			return false;
		}
		catch (...)
		{
			sErrMsg = "unknown error";
			printf("%s\n", sErrMsg.c_str());
			system("pause");
			return false;
		}
		return true;
	}

	bool Compile(ConstString s) 
	{
		String sTmpFile = s + ".lls";
		sOutFile = s + ".lua";
		if (!ast.LoadFile(s.c_str())) {
			sErrMsg = ast.GetErrorMsg();
			return false;
		}
		try {
			HeronToLLS();

			String lls = lls::PrettyPrint(module);
			PrintStringToFile(lls, sTmpFile);
			
			if (bPreprocess)
				LLSToTable();

			LLSToLua(bPreprocess);
			PrintLua(sOutFile);

			if (bPreprocess) 
				PerformPreprocess();		

			sErrMsg = "success";
			return true;
		}
		catch (CVMLException e)
		{
			sErrMsg = e.msg;
			return false;
		}
		catch (...)
		{
			sErrMsg = "unknown error";
			return false;
		}
	}

	void HeronToLLS() {
		module = heron_to_lls::Convert(ast.GetRoot());
		lls::PlaceReturnsInTailPosition(module);
	}

	void LLSToTable() {
		luavm += "ast = {\n";
		lls::LLSchemeToLuaTable tableMaker(luavm);
		tableMaker(module);
		luavm += "}\n";
	}

	void PerformPreprocess() {
		if (!luavm.GlobalExists("preprocess")) 
			return;
		luavm.Eval("preprocess()");
		lls::Module* tmp = LLSFromLua();
		delete module;
		module = tmp;
		if (bSaveLLSFile) {
			String s = lls::PrettyPrint(module);
			PrintStringToFile(s, sOutFile + ".2.lls");
		}
		luavm.Reset();
		LLSToLua(true);
		PrintLua(sOutFile + ".2.lua");
	}

	void LLSToLua(bool bPreprocess) {
		lls::LLSchemeToLua luaMaker(luavm, bPreprocess);
		luaMaker.ModuleToLua(module);
	}

	lls::Module* LLSFromLua() {
		lls::LLchemeFromLua sexprMaker(luavm);
		lls::SExpr* ast = sexprMaker.FromLuaMachine();
		lls::Module* mod = ast->GetChild(0)->As<lls::Module>();
		Assert(mod != NULL);
		return mod;
	}

	void PrintLua(ConstString sFile) {
		String s = luavm.GetPrettyText();
		PrintStringToFile(s, sFile);
	}

	String GetErrMsg() 
	{
		return sErrMsg;
	}
};

#endif
